package com.pehulja.mmdo.coursework.math;

import com.pehulja.mmdo.coursework.math.exceptions.AlgoritmExceptions.LimitlessnessOfLinearForm;
import com.pehulja.mmdo.coursework.math.matrix.Matrix;
import com.pehulja.mmdo.coursework.math.matrix.MatrixMathematics;
import com.pehulja.mmdo.coursework.math.matrix.NoSquareException;
import org.apache.commons.math.linear.*;

import java.util.*;
import java.util.Map.Entry;

/**
 * Class that implements simplex table functionality and necessary calculations
 * according to it
 */
public class SimplexTable {

    private RealMatrix matrixA;
    private List<Integer> basisComponents;
    private RealVector vectorC;
    private RealVector vectorDelta;
    private Map<Integer, Double> vectorQ;
    private int nColumnNumber;
    private int guideRowNumber, guideColumnNumber;
    private boolean optimalPlanFinded = false;
    private AbstractRealVector basicResultPlan;

    public List<Integer> getBasisComponents() {
        return basisComponents;
    }

    public void setBasisComponents(List<Integer> basisComponents) {
        this.basisComponents = basisComponents;
    }

    public double[][] getMatrixA() {
        return matrixA.getData();
    }

    public double[] getVectorC() {
        return vectorC.getData();
    }

    public double[] getVectorDelta() {
        return vectorDelta.getData();
    }

    public Map<Integer, Double> getVectorQ() {
        return vectorQ;
    }

    public boolean isOptimalPlanFinded() {
        return optimalPlanFinded;
    }

    

    public void marshallTask(LinearProgramTask task) throws LimitlessnessOfLinearForm, NoSquareException {
        vectorC = new ArrayRealVector(task.getGoalFunction().getListFactorsArray());
        /**
         * Obtaining Non extended matrix A, that consists of coefficients by
         * business restrictions components
         */
        RealMatrix matrixAfs = task.getBusinessRestrictionsMatrix(basisComponents, false);
        /**
         * Vector that helps to choose the directing row
         */
        nColumnNumber = task.getGoalFunction().getVariablesCount() + 1;
        /**
         * Converting matrix A to Array2DRowRealMatrix format
         */
        if(!checkIfIsIdentityMatrix(matrixAfs.getData()))
            matrixAfs = new Array2DRowRealMatrix(MatrixMathematics.inverse(new Matrix(matrixAfs.getData())).getValues());
        /**
         * Counting matrix A by multiplication inverse matrix A on extended
         * matrix
         */
        matrixA = matrixAfs.multiply(task.getBusinessRestrictionsMatrix(true));
    }

    public SimplexTable copy() {
        SimplexTable result = new SimplexTable();
        if(this.basicResultPlan!=null)
            result.basicResultPlan = this.basicResultPlan.copy();
        result.basisComponents = new ArrayList<Integer>();
        for (Integer item : this.basisComponents) {
            result.basisComponents.add(item.intValue());
        }
        result.matrixA = this.matrixA.copy();
        result.optimalPlanFinded = this.optimalPlanFinded;
        result.vectorC = this.vectorC.copy();
        if(this.vectorDelta!=null)
            result.vectorDelta = this.vectorDelta.copy();
        if(this.vectorQ!=null){
        result.vectorQ = new HashMap<Integer, Double>();
        for(Entry<Integer, Double> entry:this.vectorQ.entrySet())
            result.vectorQ.put(entry.getKey(), entry.getValue());
        }
        return result;
    }

    /**
     * Searching optimal plan of Linear program task using "Dual simplex method"
     *
     * @return vector of components of optimal plan components
     */
    public void count() throws LimitlessnessOfLinearForm, NoSquareException {

        /**
         * Starting searching for basic plan by recounting simplex table in loop
         */
        double tempDelta;

        //Checking situation 1 - All Xi0 >=0
        vectorQ = new HashMap<Integer, Double>();
        optimalPlanFinded = true;
        boolean isSit2;
        try{
        for (int i = 0; i < basisComponents.size(); i++) {
            if (matrixA.getEntry(i, 0) < 0) {
                optimalPlanFinded = false;
                /**
                 * Checking Situation 2 - is vector Q {Xij<0} - empty
                 */
                isSit2 = true;
                for (int j = 1; j < nColumnNumber; j++) {
                    if (matrixA.getEntry(i, j) < 0) {
                        isSit2 = false;
                        break;
                    }
                }
                if (isSit2) {
                    throw new LimitlessnessOfLinearForm();
                }
            }
        }
        }catch(Exception ex){
            ex.printStackTrace();
        }
        
            /**
             * If not resuming counting
             */
            vectorDelta = new ArrayRealVector();
            /**
             * Сalculation vector delta
             */
            for (int i = 0; i < nColumnNumber; i++) {
                tempDelta = 0;
                for (int j = 0; j < basisComponents.size(); j++) {
                    tempDelta += vectorC.getEntry(basisComponents.get(j)) * matrixA.getEntry(j, i);
                 }   
                    if (/*(j + 1) == basisComponents.size() &&*/ i != 0) {
                        tempDelta -= vectorC.getEntry(i - 1);
                    }
                
                vectorDelta = vectorDelta.append(tempDelta);
            }
        if (optimalPlanFinded) {
            basicResultPlan = new ArrayRealVector(matrixA.getColumn(0)); // Basic plan has been found
            return;
        } else {
            /**
             * Searching for directing row number
             */
            guideRowNumber = 0;
            for (int i = 1; i < basisComponents.size(); i++) {
                if (matrixA.getEntry(guideRowNumber, 0) >= matrixA.getEntry(i, 0)) {
                    guideRowNumber = i;
                }
            }
            /**
             * Calculation vector Q
             */
            for (int i = 1; i < nColumnNumber; i++) {
                if (matrixA.getEntry(guideRowNumber, i) < 0) {
                    vectorQ.put(i-1, (-1 * vectorDelta.getEntry(i) / matrixA.getEntry(guideRowNumber, i)));
                }
            }
            double minElInVectorQ = Collections.min(vectorQ.values());
            // getting index of minimal element
            
            List <Integer> possibleGuideColumns = new ArrayList<Integer>();
            for (Map.Entry<Integer, Double> e : vectorQ.entrySet()) {
                if (e.getValue() == minElInVectorQ) {
                    possibleGuideColumns.add(e.getKey());
                    break;
                }
            }
            guideColumnNumber = Collections.min(possibleGuideColumns);
        }
    }
    public void countN() throws LimitlessnessOfLinearForm, NoSquareException {

        /**
         * Starting searching for basic plan by recounting simplex table in loop
         */
        double tempDelta;

        //Checking situation 1 - All Xi0 >=0
        vectorQ = new HashMap<Integer, Double>();
        optimalPlanFinded = true;
        
        
            /**
             * If not resuming counting
             */
            vectorDelta = new ArrayRealVector();
            /**
             * Сalculation vector delta
             */
            for (int i = 0; i < nColumnNumber; i++) {
                tempDelta = 0;
                for (int j = 0; j < basisComponents.size(); j++) {
                    tempDelta += vectorC.getEntry(basisComponents.get(j)) * matrixA.getEntry(j, i);
                 }   
                    if (/*(j + 1) == basisComponents.size() &&*/ i != 0) {
                        tempDelta -= vectorC.getEntry(i - 1);
                    }
                
                vectorDelta = vectorDelta.append(tempDelta);
            }
            double []vectorDeltaData = vectorDelta.getData();
            boolean isSit2;
        try{
        for (int i = 1; i < vectorDeltaData.length; i++) {
            if (vectorDeltaData[i] > 0) {
                optimalPlanFinded = false;
                /**
                 * Checking Situation 2 - is vector Q {Xij<0} - empty
                 */
                isSit2 = true;
                /*for (int j = 1; j < nColumnNumber; j++) {
                    if (matrixA.getEntry(i, j) < 0) {
                        isSit2 = false;
                        break;
                    }
                }*/
                if (isSit2) {
                    throw new LimitlessnessOfLinearForm();
                }
            }
        }
        }catch(Exception ex){
            ex.printStackTrace();
        }
            
        if (optimalPlanFinded) {
            basicResultPlan = new ArrayRealVector(matrixA.getColumn(0)); // Basic plan has been found
            return;
        } else {
            /**
             * Searching for directing row number
             */
             for (int i = 1; i < nColumnNumber; i++) {
                if (vectorDeltaData[i] > 0) {
                    vectorQ.put(i-1, vectorDeltaData[i]);
                }
            }
            double maxElInVectorQ = Collections.min(vectorQ.values());
            
            List <Integer> possibleGuideColumns = new ArrayList<Integer>();
            for (Map.Entry<Integer, Double> e : vectorQ.entrySet()) {
                if (e.getValue() == maxElInVectorQ) {
                    possibleGuideColumns.add(e.getKey());
                    break;
                }
            }
            guideColumnNumber = Collections.min(possibleGuideColumns);
            vectorQ = new HashMap<Integer, Double>();                        
            for (int i = 1; i < basisComponents.size(); i++) {
                if (matrixA.getEntry(i, guideColumnNumber+1) > 0) {
                    vectorQ.put(i, (-1 * vectorDelta.getEntry(guideColumnNumber) / matrixA.getEntry(i, guideColumnNumber+1)));
                }
            }
            maxElInVectorQ = Collections.max(vectorQ.values());
            // getting index of minimal element
            
            List <Integer> possibleGuideRows = new ArrayList<Integer>();
            for (Map.Entry<Integer, Double> e : vectorQ.entrySet()) {
                if (e.getValue() == maxElInVectorQ) {
                    possibleGuideColumns.add(e.getKey());
                    break;
                }
            }
            guideRowNumber = Collections.min(possibleGuideColumns);
            /**
             * Calculation vector Q
             */
           
            // getting index of minimal element
            
            
        }
    }
 
    /**
     * Recurrent recalculation matrix A
     *
     * @param matrixA Matrix A that will be recalculated
     * @param guideRow
     * @param guideColumn
     * @param basisComponents List of basis components that will be changed
     * according to guideRow and guideColumn
     * @return
     */
    public void recountMatrixA() {
        System.out.println(basisComponents.toString());
        RealMatrix updMatrixA = new Array2DRowRealMatrix(matrixA.getColumn(0).length, matrixA.getRow(0).length);
        double guideElement = matrixA.getEntry(guideRowNumber, guideColumnNumber+1);
        int columnNumber = matrixA.getRow(0).length;
        for (int i = 0; i < basisComponents.size(); i++) {
            for (int j = 0; j < columnNumber; j++) {
                if (i == guideRowNumber) {
                    updMatrixA.setEntry(i, j, matrixA.getEntry(i, j) / guideElement);
                } else {
                    double a = matrixA.getEntry(i, j);
                    double b = matrixA.getEntry(i, guideColumnNumber+1);
                    double c = matrixA.getEntry(guideRowNumber, j);
                    updMatrixA.setEntry(i, j, a - b * c / guideElement);
                }
            }
        }
        for (int i = 0; i < basisComponents.size(); i++) {
            if (i == guideRowNumber) {
                basisComponents.set(i, guideColumnNumber);
                break;
            }
        }
        for(int i = 0; i<basisComponents.size(); i++)
            for(int j = 0; j < basisComponents.size()-1; j++)
            {
                if(basisComponents.get(j).intValue() > basisComponents.get(j+1).intValue()){
                    int temp = basisComponents.get(j).intValue();
                    basisComponents.set(j, basisComponents.get(j+1).intValue());
                    basisComponents.set(j+1, temp);
                    double[]tempVals = updMatrixA.getRow(j);
                    updMatrixA.setRow(j, updMatrixA.getRow(j + 1));
                    updMatrixA.setRow(j+1, tempVals);
                }
            }
        matrixA = updMatrixA;
    }
    
    /* Getting results */
    
    public Double getFunctionOptimalValue(){
        if(isOptimalPlanFinded())
            return vectorDelta.getData()[0];
        else
            return null;
    }
    
    public List<Integer> getBasisVectorsId(){
        if(isOptimalPlanFinded())
            return basisComponents;
        else
            return null;
    }
    
    public double[] getValuesOptimalResultPlanBasicComponents() {
        if(isOptimalPlanFinded())
            return basicResultPlan.getData();
        else
            return null;
    }
    
    public double[] getOptimalPlan() {
        if(isOptimalPlanFinded()){
            int n = vectorC.getData().length;
            double[] data = basicResultPlan.getData();
            double[] plan = new double[n];
            for(int i = 0, j=0; i < n; i++){
                if(basisComponents.contains(i)){
                    plan[i] = data[j];
                    j++;
                }else{
                    plan[i] = 0;
                }
            }
            return plan;
        }
        else
            return null;
    }
    
    private boolean checkIfIsIdentityMatrix(double[][]matrix){
        int n = matrix.length;
        int m = matrix[0].length;
        boolean flag = true;
        if(n != m)
            return false;
        double value = matrix[0][0];
        for(int i = 0; i < n; i++)
            for(int j =0; j<m; j++){
                if(i==j)
                    flag = matrix[i][j]==value;
                else
                    flag = matrix[i][j]==0.0;
                if(!flag)
                    return flag;
            }
        return flag;
    }
}
